package pl.edu.pw.stud.ltrzaska.logic;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;

import pl.edu.pw.stud.ltrzaska.exception.FilesysPolicyException;
import pl.edu.pw.stud.ltrzaska.policy.entity.Filesystem;
import pl.edu.pw.stud.ltrzaska.policy.entity.FilesystemObject;
import pl.edu.pw.stud.ltrzaska.policy.entity.PatternObject;
import pl.edu.pw.stud.ltrzaska.policy.entity.Person;
import pl.edu.pw.stud.ltrzaska.policy.entity.Policy;
import pl.edu.pw.stud.ltrzaska.policy.entity.iface.IFilesystemParent;
import pl.edu.pw.stud.ltrzaska.policy.xml.XmlConverter;

/**
 * Odpowiada za zaladowanie polityki poprawnosci, na ktore sklada sie
 * sparsowanie pliku XML, utworzenie listy nazwa, odnalezienie referencji do
 * obiektow w ramach polityki itp.
 * 
 * @author luk
 * 
 */
public class PolicyLoader {
	private static final Logger LOGGER = Logger.getLogger(PolicyLoader.class);
	private Set<String> uniqueRefNames = new HashSet<String>();
	private Map<String, Object> objects = new HashMap<String, Object>();

	private Policy policy;

	public PolicyLoader(File f) throws FilesysPolicyException {
		policy = loadFromFile(f);
	}

	private void addUniqueRefName(String uniqueRefName) {
		this.uniqueRefNames.add(uniqueRefName);
	}

	private Set<String> getUniqueRefNames() {
		return uniqueRefNames;
	}

	public static Policy toRawPolicy(File file) {
		String xml = null;
		Policy po = null;
		try {
			InputStream in = new java.io.FileInputStream(file);
			xml = IOUtils.toString(in);
		} catch (FileNotFoundException e) {
			LOGGER.error("", e);
		} catch (IOException e) {
			LOGGER.error("", e);
		}
		if (xml != null) {
			po = (Policy) XmlConverter.toPolicy(xml);
		}
		return po;
	}

	private void preparePolicyObjects() throws FilesysPolicyException {
		preparePeopleSection();

		Filesystem fs = policy.getFilesystem();

		prepareNodeNames(fs);
		createNodeReferences(fs);
	}

	private void preparePeopleSection() throws FilesysPolicyException {
		for (Person p : policy.getPeople()) {
			if (uniqueRefNames.contains(p.getRefName())) {// < nazwarefname musi
															// byc unikalna
				throw new FilesysPolicyException(new StringBuffer("Zduplikowany atrybut \"refname\" dla obiektu polityki: ").append(p.getRefName()).toString());
			} else {
				addObject(p.getRefName(), p);
			}
		}
	}

	private void prepareNodeNames(FilesystemObject policyFsObj) throws FilesysPolicyException {
		if (policyFsObj.getRefname() == null) { // < atrybut refname
												// obligatoryjny
			StringBuffer sb = new StringBuffer("Obiekt polityki musi miec nadany atrybut \"refname\".");
			if (policyFsObj.getName() != null) {
				sb.append(" name:").append(policyFsObj.getName());
			}
			throw new FilesysPolicyException(sb.toString());
		}
		if (uniqueRefNames.contains(policyFsObj.getRefname())) {// <
																// nazwarefname
																// musi byc
																// unikalna
			throw new FilesysPolicyException(new StringBuffer("Zduplikowany atrybut \"refname\" dla obiektu polityki: ").append(policyFsObj.getRefname()).toString());
		} else {
			addObject(policyFsObj.getRefname(), policyFsObj);
		}

		if (policyFsObj.hasChildren()) {
			IFilesystemParent policyFsParent = (IFilesystemParent) policyFsObj;
			if (null != policyFsParent.getChildren()) {
				for (FilesystemObject childObj : policyFsParent.getChildren()) {
					this.prepareNodeNames(childObj);
				}
			}
		}
	}

	private void createNodeReferences(FilesystemObject policyFsObj) throws FilesysPolicyException {
		String tmp = null;
		if ((tmp = policyFsObj.getOwnerRefName()) != null) {
			if (!getUniqueRefNames().contains(policyFsObj.getOwnerRefName())) {
				throw new FilesysPolicyException(new StringBuffer("Ustawienie atrybutu owner na nazwe Person, ktora nie istnieje: ")
						.append(policyFsObj.getOwnerRefName()).toString());
			} else {
				policyFsObj.setOwner((Person) this.getObject(tmp));
			}
		}
		if ((tmp = policyFsObj.getInherits()) != null) {
			if (!getUniqueRefNames().contains(policyFsObj.getInherits())) {
				throw new FilesysPolicyException();
			} else {
				policyFsObj.inheritAttributes((PatternObject) this.getObject(tmp));
			}
		}

		if (policyFsObj.hasChildren()) {
			IFilesystemParent policyFsParent = (IFilesystemParent) policyFsObj;
			if (null != policyFsParent.getChildren()) {

				for (FilesystemObject childObj : policyFsParent.getChildren()) {
					childObj.setParent(policyFsObj);
					this.createNodeReferences(childObj);
				}
			}
		}
	}

	public Policy getPolicy() {
		return policy;
	}

	private Policy loadFromFile(File f) throws FilesysPolicyException {
		policy = toRawPolicy(f);
		preparePolicyObjects();
		return policy;
	}

	public void addObject(String key, Object value) {
		addUniqueRefName(key);
		this.objects.put(key, value);
	}

	public Object getObject(String refName) {
		return objects.get(refName);
	}
}
